/*******************************************************************************
 * copyright (c) 2009 - 2010 Simon Allen
 *
 * This file is part of Convex Media Converter.
 *
 * Convex Media Converter is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * Convex Media Converter is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with Convex Media Converter; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 ******************************************************************************/


#include "../constants.h"
#include "convertdialog.h"
#include "../OutputSettings/outputsettings.h"
#include "../Preferences/preferences.h"
#include "ui_convertdialog.h"

#include <iostream>
#include <new>
#include <unistd.h>

#include <qprocess.h>
#include <QFile>

#ifndef _WIN32
	#include <QtDBus>
#endif

using namespace std;


ConvertDialog::ConvertDialog(PresetSettings *newSettings, QWidget *parent):
		QDialog(parent), ui(new Ui::ConvertDialog)
{
	fileExistsAction = ASK;
	filesDone = 0;
	duration = 0;
	totalDuration = 0;
	ffmpeg = NULL;
	settings = newSettings;

#ifdef unix
	notify_init("Conversion Successful");

	notification = notify_notification_new("Convex Media Converter",
		"All files have been successfully converted.", CONVEX_LOGO, NULL);

	if (notification)
		notify_notification_set_timeout(notification, 3000);

	else
		qDebug("Failed to create notification.");
#endif

	ui->setupUi(this);

	setWindowIcon(QIcon(CONVEX_LOGO));

	textEdit_details = new QTextEdit(this);

	textEdit_details->hide();
	textEdit_details->setFixedHeight(200);
	textEdit_details->setTextInteractionFlags(Qt::TextSelectableByKeyboard);

	connect(ui->buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(finishConversion(QAbstractButton *)));
}

ConvertDialog::~ConvertDialog()
{
	if (notification)
	{
		/* Clean up the memory */
		g_object_unref(notification);
		notify_uninit();
	}

	delete ui;

	ui = NULL;
}

QStringList ConvertDialog::getConversionArgs()
{
	QStringList args;
	int suffix = 1;

	currentOutputFile = outputSettings.getOutputDir() + "/" +
			  currentInputFileIter->completeBaseName() + outputSettings.getExtension();

	while (QFileInfo(currentOutputFile).exists())
	{
		currentOutputFile = outputSettings.getOutputDir() + "/" +
				    currentInputFileIter->completeBaseName() + " (" +
				    QString::number(suffix) + ")" + outputSettings.getExtension();

		suffix++;
	}

	//args << "-threads" << QString::number(countProcs());
	args << "-i" << currentInputFileIter->filePath();

	/*args << "-r" << "29.97" << "-vcodec" << "libxvid";
	args << "-s" << "704x384" << "-aspect" << "16:9";
	args << "-maxrate" << "1500k" << "-b" << "1250k";
	args << "-qmin" << "3" << "-qmax" << "5";
	args << "-bufsize" << "4096" << "-mbd" << "2";
	args << "-flags" << "+4mv" << "-trellis";
	args << "-aic" << "-cmp" << "2" << "-subcmp";
	args << "2" << "-g" << "300";*/

	if (outputSettings.getFormat() == "dvd")

	if (outputSettings.hasAudio())
	{
		args << FFMPEG_AUDIO_CODEC << outputSettings.getAudioCodec();

		if ((outputSettings.getAudioCodec() != "pcm_u8") &&
		    (outputSettings.getAudioCodec() != "flac") &&
		    (outputSettings.getAudioMode() != OutputSettings::AUDIO_MODE_VBR))
			args << FFMPEG_AUDIO_BITRATE << QString::number(
					outputSettings.getAudioBitrate()) + 'k';

		if (outputSettings.getAudioMode() == OutputSettings::AUDIO_MODE_VBR)
		{
			args << FFMPEG_AUDIO_QUALITY;

			int bitrate = outputSettings.getAudioBitrate();
			QString audioCodec = outputSettings.getAudioCodec();

			if (audioCodec == OutputSettings::AUDIO_CODEC_MP3)
			{
				if (bitrate <= OutputSettings::AUDIO_BITRATE_LOW.toInt())
					args << QString::number(OutputSettings::AUDIO_MP3_VBR_LOW);

				else if (bitrate <= OutputSettings::AUDIO_BITRATE_NORMAL.toInt())
					args << QString::number(OutputSettings::AUDIO_MP3_VBR_NORMAL);

				else if (bitrate <= OutputSettings::AUDIO_BITRATE_HIGH.toInt())
					args << QString::number(OutputSettings::AUDIO_MP3_VBR_HIGH);

				else
					args << QString::number(OutputSettings::AUDIO_MP3_VBR_VERY_HIGH);
			}

			else if (audioCodec == OutputSettings::AUDIO_CODEC_VORBIS)
			{
				if (bitrate <= OutputSettings::AUDIO_BITRATE_VERY_LOW.toInt())
					args << QString::number(OutputSettings::AUDIO_VORBIS_VBR_VERY_LOW);

				else if (bitrate <= OutputSettings::AUDIO_BITRATE_LOW.toInt())
					args << QString::number(OutputSettings::AUDIO_VORBIS_VBR_LOW);

				else if (bitrate <= OutputSettings::AUDIO_BITRATE_NORMAL.toInt())
					args << QString::number(OutputSettings::AUDIO_VORBIS_VBR_NORMAL);

				else if (bitrate <= OutputSettings::AUDIO_BITRATE_HIGH.toInt())
					args << QString::number(OutputSettings::AUDIO_VORBIS_VBR_HIGH);

				else if (bitrate <= OutputSettings::AUDIO_BITRATE_VERY_HIGH.toInt())
					args << QString::number(OutputSettings::AUDIO_VORBIS_VBR_VERY_HIGH);

				else
					args << QString::number(OutputSettings::AUDIO_VORBIS_VBR_ULTRA_HIGH);
			}

			else if (audioCodec == OutputSettings::AUDIO_CODEC_AAC)
			{
				if (bitrate <= OutputSettings::AUDIO_BITRATE_VERY_LOW.toInt())
					args << QString::number(OutputSettings::AUDIO_AAC_VBR_VERY_LOW);

				else if (bitrate <= OutputSettings::AUDIO_BITRATE_LOW.toInt())
					args << QString::number(OutputSettings::AUDIO_AAC_VBR_LOW);

				else if (bitrate <= OutputSettings::AUDIO_BITRATE_NORMAL.toInt())
					args << QString::number(OutputSettings::AUDIO_AAC_VBR_NORMAL);

				else if (bitrate <= OutputSettings::AUDIO_BITRATE_HIGH.toInt())
					args << QString::number(OutputSettings::AUDIO_AAC_VBR_HIGH);

				else
					args << QString::number(OutputSettings::AUDIO_AAC_VBR_VERY_HIGH);
			}
		}

		args << FFMPEG_AUDIO_SAMPLE_RATE << QString::number(
				outputSettings.getAudioSampleRate());
		args << FFMPEG_AUDIO_CHANNELS << QString::number(
				outputSettings.getAudioChannels());
	}

	else
		args << FFMPEG_DISABLE_AUDIO;

	if (outputSettings.hasVideo())
	{
		args << FFMPEG_VIDEO_CODEC << outputSettings.getVideoCodec();
		args << FFMPEG_VIDEO_BITRATE << QString::number(
				outputSettings.getVideoBitrate()) + "kb";
		args << FFMPEG_VIDEO_SIZE << QString::number(
				outputSettings.getWidth()) + "x" +
				QString::number(outputSettings.getHeight());
		args << FFMPEG_FRAME_RATE << QString::number(
				outputSettings.getFrameRate());
		args << FFMPEG_VIDEO_ASPECT << outputSettings.getAspect();
	}

	else
		args << FFMPEG_DISABLE_VIDEO;

	args << FFMPEG_MAP_METADATA << currentOutputFile + ":" + currentInputFileIter->absoluteFilePath();

	QString cmdOptions = outputSettings.getCmdOptions();

	if (!cmdOptions.isEmpty())
		args << (outputSettings.getCmdOptions().split(' '));

	args << currentOutputFile;

	return args;
}

int ConvertDialog::countProcs() const
{
#ifdef _SC_NPROCESSORS_ONLN

	int numProcs = sysconf(_SC_NPROCESSORS_ONLN);

	if (numProcs < 1)
	{
		cerr << "Convex: Could not determine number of CPUs online. Assuming one thread.\n";

		numProcs = 1;
	}

#else

	cerr << "Convex: Could not determine number of CPUs online. Assuming one thread.\n";

	int numProcs = 1;

#endif

	return numProcs;
}

void ConvertDialog::convert(const QString newFFmpegLocation, const QList<QFileInfo> files,
			    const OutputSettings newSettings)
{
	FFmpegLocation = newFFmpegLocation;
	inputFiles = files;
	outputSettings = newSettings;
	filesDone = 0;
	QList<QFileInfo>::iterator iter = inputFiles.begin();

	ui->label_desc->setText("Your media files are now being converted.");

	while (iter != inputFiles.end())
	{
		bool canConvert = true;

		QFileInfo outputFile(outputSettings.getOutputDir() + "/" +
				     iter->completeBaseName() +
				     outputSettings.getExtension());

		if (outputFile.exists())
		{
			switch (fileExistsAction)
			{
				case DELETE:
					deleteFile(outputFile.absoluteFilePath());

					break;

				case PRESERVE:
					iter = inputFiles.erase(iter);

					canConvert = false;

					break;

				case ASK:
					QMessageBox::StandardButton pressed = QMessageBox::warning(this, "File output already exists",
								 "The file " + outputFile.fileName() + " already exists. "
								 "Do you want to overwrite the file?", QMessageBox::No |
								 QMessageBox::NoToAll | QMessageBox::Yes | QMessageBox::YesToAll);

					switch (pressed)
					{
						case QMessageBox::Yes:
							deleteFile(outputFile.absoluteFilePath());

							break;

						case QMessageBox::YesToAll:
							fileExistsAction = DELETE;

							deleteFile(outputFile.absoluteFilePath());

							break;

						case QMessageBox::NoToAll:
							fileExistsAction = PRESERVE;

							canConvert = false;

							iter = inputFiles.erase(iter);

							break;

						default:
							canConvert = false;

							iter = inputFiles.erase(iter);
					}
			}
		}

		if (canConvert)
		{
			MediaInfo info;
			info.Open(iter->filePath().toStdString());

			totalDuration += QString(info.Get(Stream_General, 0, "Duration").c_str()).toLong() / 1000;

			iter++;
		}
	}

	if (inputFiles.size() > 0)
	{
		currentInputFileIter = inputFiles.begin();

		convertNextFile();
	}

	else
	{

		// No files to convert as all the output files exist already...
	}
}

void ConvertDialog::convertNextFile()
{
	ffmpeg = new QProcess(this);

	connect(ffmpeg, SIGNAL(finished(int, QProcess::ExitStatus)),
					  this, SLOT(fileConverted(int, QProcess::ExitStatus)));
	connect(ffmpeg, SIGNAL(readyRead()), this, SLOT(readFromStdout()));
	connect(ui->pushButton_details, SIGNAL(toggled(bool)), this, SLOT(detailsToggled(bool)));

	ffmpeg->setProcessChannelMode(QProcess::MergedChannels);

	ui->label_currentFile->setText(currentInputFileIter->fileName());
	ui->label_filesDone->setText(QString::number(filesDone) + " / " +
				     QString::number(inputFiles.size()) + " converted");

	QStringList args = getConversionArgs();

	textEdit_details->append("<FONT COLOR=blue>Running conversion command:</FONT>");
	command = "<FONT COLOR=green>" + FFmpegLocation;

	for (QStringList::const_iterator argIter = args.constBegin(); argIter != args.constEnd(); argIter++)
	{
		command += " ";
		command += *argIter;
	}

	command += "<br></FONT>";

	textEdit_details->append(command);

	MediaInfo info;

	info.Open(currentInputFileIter->filePath().toStdString());

	duration = QString(info.Get(Stream_General, 0, "Duration").c_str()).toLong() / 1000;

	ffmpeg->start(FFmpegLocation, args);
}

void ConvertDialog::readFromStdout()
{
	QString line = ffmpeg->readAll();

	textEdit_details->append(line);

	if (line.contains(" time="))
	{
		int startTimeDone = line.indexOf(" time=") + 6;

		if (startTimeDone > -1)
		{
			line.remove(0, startTimeDone);
			int endTimeDone = line.indexOf(" ");
			line.truncate(endTimeDone);
			double timeDone = line.toDouble();
			int percentDone = static_cast<int>(timeDone / duration * 100);

			if (percentDone > 99)
				ui->progressBar_current->setValue(99);

			else
				ui->progressBar_current->setValue(percentDone);

			double totalTimeDone = timeDone;

			for (QList<QFileInfo>::const_iterator iter = inputFiles.begin();
			iter != currentInputFileIter; iter++)
			{
				MediaInfo info;

				info.Open(iter->filePath().toStdString());

				totalTimeDone += (QString(info.Get(Stream_General, 0, "Duration").c_str()).toLong() / 1000);
			}

			int totalPercentDone = static_cast<int>(totalTimeDone / totalDuration * 100);

			if (totalPercentDone > 99)
				ui->progressBar_total->setValue(99);

			else
				ui->progressBar_total->setValue(totalPercentDone);
		}
	}
}

void ConvertDialog::fileConverted(int exitCode, QProcess::ExitStatus exitStatus)
{
	filesDone++;
	currentInputFileIter++;

	/*
		Process exited normally, no crash.
		FFmpeg may still have exited in error if exitCode != 0,
		but it was nice and obeyed termination.
	*/
	if (exitStatus == QProcess::NormalExit)
	{
		// FFmpeg exited without error.
		if (exitCode == 0)
		{
			ui->progressBar_current->setValue(100);

			ui->label_filesDone->setText(QString::number(filesDone) + " / " +
						     QString::number(inputFiles.size()) + " converted");

			if (currentInputFileIter != inputFiles.end())
				convertNextFile();

			else
			{
				if (settings->value(Preferences::SETTING_SHUTDOWN_WHEN_DONE).toBool())
					shutdownComputer();

				else if (settings->value(Preferences::SETTING_SHOW_NOTIFICATIONS).toBool())
					showFinishedNotification();

				QString msg = "All files have been successfully converted.\nPress Ok to continue.";

				ui->progressBar_total->setValue(100);
				ui->label_desc->setText(msg);

				ui->buttonBox->clear();
				ui->buttonBox->addButton(QDialogButtonBox::Ok);
			}
		}

		// must work on this part. If codecs not found ffmpeg returns error
		// should tell user but also this will be executed if user cancelled conversion
		else
		{
			//close();
			//deleteFile(currentInputFileIter);
		}
	}

	// FFmpeg process died unexpectantly (crashed).
	else
	{
		QString errorMsg("An unexpected crash has been detected while converting the file ");

		errorMsg += currentInputFileIter->fileName() + ". The result of this conversion may now "
			    "be corrupted. Do you want to delete the corrupted output file?";

		QMessageBox::StandardButton pressed = QMessageBox::critical(this, "Failed to convert media file",
									    errorMsg, QMessageBox::Yes |
									    QMessageBox::No);

		if (pressed == QMessageBox::Yes)
			deleteFile(currentOutputFile);

		if (currentInputFileIter != inputFiles.end())
			convertNextFile();

		else
		{
			QString msg = "All files have been successfully converted.\nPress Ok to continue.";

			ui->progressBar_total->setValue(100);
			ui->label_desc->setText(msg);

			ui->buttonBox->clear();
			ui->buttonBox->addButton(QDialogButtonBox::Ok);
		}
	}
}

void ConvertDialog::detailsToggled(const bool on)
{
	if (on)
	{
		setMaximumSize(600, 500);
		resize(600, 500);

		ui->verticalLayout->insertWidget(8, textEdit_details);
		textEdit_details->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum);

		textEdit_details->show();
	}

	else
	{
		textEdit_details->hide();

		resize(600, 270);
	}
}

void ConvertDialog::finishConversion(QAbstractButton *button)
{
	if (button->text() == "&OK" || button->text() == "OK")
	{
		close();
		clear();
	}

	else
	{
		QString text = "Are you sure you want to cancel converting your media files?"
			       "\n\nConvex Media Converter will automatically delete any output files "
			       "generated during this conversion process.";

		QMessageBox::StandardButton pressed = QMessageBox::warning(this, "Cancel Conversion",
					 text, QMessageBox::No | QMessageBox::Yes);

		if (pressed == QMessageBox::Yes)
		{
			ffmpeg->terminate(); // Try to be nice to process.

			// HOW DARE YOU DEFY MY DIETY POWERS OF SUPREMACY!
			QTimer::singleShot(3000, ffmpeg, SLOT(kill())); // Force termination.
			// How you feelin' now?

			deleteFile(currentOutputFile);

			close();
			clear();
		}
	}
}

void ConvertDialog::shutdownComputer()
{
#ifdef _WIN32
	QProcess::startDetached("shutdown -s -t 80");

#else
	bool shutdownWorked = false;
	QDBusMessage response;

	QDBusInterface kdeSessionManager("org.kde.ksmserver", "/KSMServer",
					 "org.kde.KSMServerInterface",
					 QDBusConnection::sessionBus());

	response = kdeSessionManager.call("logout", 0, 2, 2);

	if (response.type() != QDBusMessage::ErrorMessage)
		shutdownWorked = true;

	QDBusInterface gnomeSessionManager("org.gnome.SessionManager",
					   "/org/gnome/SessionManager",
					   "org.gnome.SessionManager",
					   QDBusConnection::sessionBus());

	response = gnomeSessionManager.call("RequestShutdown");

	if (response.type() != QDBusMessage::ErrorMessage)
		shutdownWorked = true;

	if (!shutdownWorked)
	{
		QDBusInterface powermanagement("org.freedesktop.Hal",
					       "/org/freedesktop/Hal/devices/computer",
						"org.freedesktop.Hal.Device.SystemPowerManagement",
						QDBusConnection::systemBus());

		response = powermanagement.call("Shutdown");

		if (response.type() == QDBusMessage::ErrorMessage)
		{
			QString msg("Convex Media Convert was unable to shutdown your computer."
				    "\n\nThis may be because you do not have permission to do so"
				    " or other users are currently logged in.");

			QMessageBox::warning(this, "Failed to shutdown", msg);
		}
	}
#endif
}

void ConvertDialog::showFinishedNotification() const
{
#ifdef _WIN32
	QMessageBox::information(this, "Convex Media Converter",
				 "All files have been successfully converted.");
#else
	if (notification)
	{
		/* Schedule notification for showing */
		if (!notify_notification_show(notification, NULL))
			qDebug("Failed to send notification.");
	}
#endif
}

void ConvertDialog::deleteFile(const QString outputFile) const
{
	QFile::remove(outputFile);
}

void ConvertDialog::clear()
{
	filesDone = 0;
	duration = 0;
	totalDuration = 0;
	textEdit_details->clear();;
	inputFiles.clear();
	currentInputFileIter = inputFiles.begin();
	fileExistsAction = ASK;

	ui->label_desc->clear();
	ui->label_currentFile->clear();
	ui->label_filesDone->clear();

	ui->progressBar_current->setValue(0);
	ui->progressBar_total->setValue(0);
}
